using System;
using System.Collections;
using System.Collections.Generic;
using System.Drawing;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Windows.Forms;
using ZoneFiveSoftware.Common.Data;
using ZoneFiveSoftware.Common.Data.Measurement;
using ZoneFiveSoftware.Common.Data.Fitness;
using ZoneFiveSoftware.Common.Data.GPS;
using ZoneFiveSoftware.Common.Visuals;
using ZoneFiveSoftware.Common.Visuals.Util;
using STCommon.Data;
using ZoneFiveSoftware.Common.Visuals.Fitness;

namespace STCommon.Controller
{
    public class Utils
    {
        [DllImport("gdi32.dll")]
        private static extern int BitBlt(IntPtr hdcDest, int nXDest, int nYDest, int nWidth, int nHeight,
                                         IntPtr hdcSrc, int nXSrc, int nYSrc, Int32 dwRop);

        [DllImport("gdi32.dll")]
        private static extern IntPtr CreateCompatibleDC(IntPtr hDC);

        [DllImport("gdi32.dll")]
        private static extern bool DeleteDC(IntPtr hdc);

        [DllImport("gdi32.dll")]
        private static extern bool DeleteObject(IntPtr hObject);

        [DllImport("gdi32.dll")]
        private static extern IntPtr SelectObject(IntPtr hDC, IntPtr hObject);

        static public void RenderBitmapToGraphics(Bitmap source, Graphics destination, Rectangle destinationRect)
        {
            try
            {
                IntPtr destHdc = destination.GetHdc();
                IntPtr bitmapHdc = CreateCompatibleDC(destHdc);
                IntPtr hBitmap = source.GetHbitmap();
                IntPtr oldHdc = SelectObject(bitmapHdc, hBitmap);

                BitBlt(destHdc, destinationRect.Left, destinationRect.Top,
                       destinationRect.Width, destinationRect.Height,
                       bitmapHdc, 0, 0, 0x00CC0020);


                destination.ReleaseHdc(destHdc);
                SelectObject(bitmapHdc, oldHdc);
                DeleteDC(bitmapHdc);
                DeleteObject(hBitmap);
            }
            catch//(System.DllNotFoundException e)
            {
                // Mono/Linux - Just go on right now
                //throw e;
            }
        }

        public static double Cursorize(double value, double min, double max)
        {
            if (min == max)
            {
                return double.NaN;
            }

            double diff = (max - min);

            return (value - min) / diff;
        }

        public static double Clamp(double value, double min, double max)
        {
            return Math.Min(max, Math.Max(value, min));
        }

        public static double CursorizeAndClamp(double value, double min, double max)
        {
            double cursor = Cursorize(value, min, max);

            return Clamp(cursor, 0, 1);
        }

        public static double Lerp(double a, double b, double cursor)
        {
            double diff = (b - a);

            return a + diff * cursor;
        }

        public static Length.Units MajorLengthUnit(Length.Units unit)
        {
            if(IsMetric(unit))
            {
                return Length.Units.Kilometer;
            }
            else
            {
                return Length.Units.Mile;
            }
        }

        public static Length.Units MinorLengthUnit(Length.Units unit)
        {
            if (IsMetric(unit))
            {
                return Length.Units.Meter;
            }
            else
            {
                return Length.Units.Yard;
            }
        }

        public static Length.Units MinorElevationUnit(Length.Units unit)
        {
            if (IsMetric(unit))
            {
                return Length.Units.Meter;
            }
            else
            {
                return Length.Units.Foot;
            }
        }

        public static bool IsMetric(Length.Units unit)
        {
            return (int)unit <= (int)Length.Units.Kilometer;
        }

        public static bool IsStatute(Length.Units unit)
        {
            return !IsMetric(unit);
        }

        public static string GetMajorSpeedUnitLabelForActivity(IActivity activity)
        {
            string speedUnitLabel = string.Empty;

            if (activity != null)
            {
                speedUnitLabel = Speed.Label(activity.Category.SpeedUnits, new Length(1, MajorLengthUnit(activity.Category.DistanceUnits)));
            }

            return speedUnitLabel;
        }

        // Returns result in unit/h
        public static double MeterPerSecToSpeed(double metersPerSecond, Length.Units toUnit)
        {
            return Length.Convert(metersPerSecond, Length.Units.Meter, toUnit) * Constants.SecondsPerHour;
        }

        // Returns result in min/unit
        public static double MeterPerSecToPace(double metersPerSecond, Length.Units toUnit)
        {
            return SpeedToPace(MeterPerSecToSpeed(metersPerSecond, toUnit));
        }

        // Returns result in unit/h
        public static double SpeedToMeterPerSec(double speed, Length.Units fromUnit)
        {
            return Length.Convert(speed, fromUnit, Length.Units.Meter) / Constants.SecondsPerHour;
        }

        // Returns result in min/unit
        public static double PaceToMeterPerSec(double paceMinutes, Length.Units fromUnit)
        {
            return SpeedToMeterPerSec(PaceToSpeed(paceMinutes), fromUnit);
        }

        // Returns result in min/toUnit
        public static double SpeedToPace(double speed, Length.Units fromUnit, Length.Units toUnit)
        {
            return SpeedToPace(Length.Convert(speed, fromUnit, toUnit));
        }

        // When using the same units, converts from unit/h to min/unit
        public static double SpeedToPace(double speed)
        {
            return Constants.MinutesPerHour / speed;
        }

        // Returns result in toUnit/h
        public static double PaceToSpeed(double pace, Length.Units fromUnit, Length.Units toUnit)
        {
            return Length.Convert(PaceToSpeed(pace), fromUnit, toUnit);
        }

        // When using the same units, converts from min/unit to unit/h
        public static double PaceToSpeed(double pace)
        {
            return Constants.MinutesPerHour / pace;
        }

        public static bool IsTextIntegerInRange(string text, Int64 minRange, Int64 maxRange)
        {
            Int64 value;

            if (Int64.TryParse(text, out value))
            {
                if (value >= minRange && value <= maxRange)
                {
                    return true;
                }
            }

            return false;
        }

        public static bool IsTextIntegerInRange(string text, Int32 minRange, Int32 maxRange)
        {
            Int32 value;

            if (Int32.TryParse(text, out value))
            {
                if (value >= minRange && value <= maxRange)
                {
                    return true;
                }
            }

            return false;
        }

        public static bool IsTextIntegerInRange(string text, UInt16 minRange, UInt16 maxRange)
        {
            UInt16 value;

            if (UInt16.TryParse(text, out value))
            {
                if (value >= minRange && value <= maxRange)
                {
                    return true;
                }
            }

            return false;
        }

        public static bool IsTextInteger(string text)
        {
            Int64 value;

            return Int64.TryParse(text, out value);
        }

        public static bool IsTextFloatInRange(string text, double minRange, double maxRange)
        {
            return IsTextFloatInRange(text, minRange, maxRange, CultureInfo.CurrentCulture);
        }

        public static bool IsTextFloatInRange(string text, double minRange, double maxRange, CultureInfo culture)
        {
            double value;

            if (double.TryParse(text, NumberStyles.Float, culture.NumberFormat, out value))
            {
                if (value < (minRange - Constants.Delta) || value > (maxRange + Constants.Delta))
                {
                    return false;
                }

                return true;
            }

            return false;
        }

        public static bool IsTextTime(string time)
        {
            try
            {
                if (time.IndexOf(':') != -1)
                {
                    string minutes = time.Substring(0, time.IndexOf(':'));
                    string seconds = time.Substring(time.IndexOf(':') + 1);

                    return IsTextInteger(minutes) &&
                           IsTextIntegerInRange(seconds, 0, 60);
                }

                float temp;
                return float.TryParse(time, out temp);
            }
            catch
            {
                return false;
            }
        }

        public static bool IsTextTimeInRange(string text, double minRange, double maxRange)
        {
            if (IsTextTime(text))
            {
                float value = TimeToFloatMinutes(text);

                if (value < (minRange - Constants.Delta) || value > (maxRange + Constants.Delta))
                {
                    return false;
                }

                return true;
            }
            else
            {
                return false;
            }
        }

        public static float TimeToFloatMinutes(string time)
        {
            if (IsTextTime(time))
            {
                if (time.IndexOf(':') != -1)
                {
                    string minutes = time.Substring(0, time.IndexOf(':'));
                    string seconds = time.Substring(time.IndexOf(':') + 1);

                    float minutesValue = 0;
                    float secondsValue = 0;
                    float value = 0;

                    if (minutes != String.Empty)
                    {
                        minutesValue = float.Parse(minutes);
                    }
                    secondsValue = float.Parse(seconds) / Constants.SecondsPerMinute;

                    if(minutesValue < 0)
                    {
                        value = minutesValue - secondsValue;
                    }
                    else
                    {
                        value = minutesValue + secondsValue;
                    }

                    return value;
                }
                else
                {
                    // We only have minutes
                    return float.Parse(time);
                }
            }

            return float.NaN;
        }

        public static String TimeMinutesToString(float timeInMinutes)
        {
            if (float.IsPositiveInfinity(timeInMinutes))
            {
                return float.PositiveInfinity.ToString();
            }
            else
            {
                int minutes = (int)Math.Truncate(timeInMinutes);

                return String.Format("{0:00}:{1:00.###}",
                                     minutes,
                                     Math.Abs(timeInMinutes - minutes) * Constants.SecondsPerMinute);
            }
        }

        public static IActivity FindActivityByDateTime(ILogbook logbook, DateTime startTime)
        {
            TimeZone localTimeZone = TimeZone.CurrentTimeZone;
            startTime = localTimeZone.ToUniversalTime(startTime);

            foreach (IActivity currentActivity in logbook.Activities)
            {
                if (currentActivity.StartTime.Equals(startTime))
                {
                    return currentActivity;
                }
            }

            return null;
        }

        public static IZoneCategory CreateUnregisteredZoneCategory(ILogbook logbook)
        {
            IZoneCategory result = null;
            bool logbookWasModified = logbook.Modified;

            // We'll use the cadence to create our isntace but it really doesn't
            //  matter at all since we'll unregister it afterwards
            result = logbook.CadenceZones.Add();

            // Remove from the list so it doesn't polute the list
            logbook.CadenceZones.Remove(result);

            result.Zones.Clear();

            // Reset the modified flag, we didn't change anything in the application
            logbook.Modified = logbookWasModified;

            return result;
        }
        
        public static void FillZoneCategoryAtInterval(IZoneCategory category,
                                                       float minimumValue,
                                                       float maximumValue,
                                                       float interval)
        {
            if (category != null)
            {
                category.Zones.Clear();

                interval = Math.Abs(interval);

                if (interval > 0)
                {
                    int minMultiplier = (int)Math.Floor(minimumValue / interval);
                    int maxMultiplier = (int)(maximumValue / interval);

                    for (int index = minMultiplier; index <= maxMultiplier; ++index)
                    {
                        NamedLowHighZone currentZone = new NamedLowHighZone(index.ToString(),
                                                                            index * interval,
                                                                            (index + 1) * interval);
                        category.Zones.Add(currentZone);
                    }
                }
            }
        }

        public static void FillZoneCategoryAtInterval(IZoneCategory category,
                                                       INumericTimeDataSeries datatrack,
                                                       float interval)
        {
            interval = Math.Abs(interval);

            if (!IsNullOrEmptyTrack(datatrack) && interval > 0)
            {
                FillZoneCategoryAtInterval(category, datatrack.Min, datatrack.Max, interval);
            }
            else if(category != null)
            {
                category.Zones.Clear();
            }
        }

        public static bool ActivityUsesCategoryOptions(IActivity activity, IActivityCategory category)
        {
            IActivityCategory currentCategory = activity.Category;

            do
            {
                if (category == currentCategory)
                {
                    return true;
                }
                else if (!currentCategory.UseParentSettings)
                {
                    return false;
                }

                currentCategory = currentCategory.Parent;
            }
            while (currentCategory != null);

            return false;
        }

        public static IList<ItemType> RemoveDuplicatesFromList<ItemType>(IList<ItemType> sourceList)
        {
            List<ItemType> result = new List<ItemType>();

            foreach (ItemType item in sourceList)
            {
                if (!result.Contains(item))
                {
                    result.Add(item);
                }
            }

            return result;
        }

        public static IList<ItemType> GetAllContainedUniqueItemsOfType<ItemType>(IEnumerable genericItems)
        {
            return RemoveDuplicatesFromList(CollectionUtils.GetAllContainedItemsOfType<ItemType>(genericItems));
        }

        public static bool IsNullOrEmptyTrack(INumericTimeDataSeries track)
        {
            return track == null || track.Count == 0;
        }

        // The data track is returned in the unit of the activity with the pauses/stops included.
        //  This means the following:
        //
        //  Cadence: Always in RPM
        //  Elevation: In the activity's category elevation unit
        //  Grade: Always in percent
        //  Heart rate: Always in BPM (use ConvertTrackToHRReferential to covnert to %Max or &HRR)
        //  Speed: Based on the activity's category distance unit and speed/pace setting.  This means the result can be
        //          in km/h for a kilometer & speed setup, in min/mile for a mile & pace setup, etc.
        //  Power: Always in watts
        public static INumericTimeDataSeries GetTrackFromActivity(IActivity activity, Enums.SportTracksDataTrackTypes dataType)
        {
            INumericTimeDataSeries result = null;

            if (activity != null)
            {
                ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity);
                IActivityCategory activityCategory = activity.Category;

                switch (dataType)
                {
                    case Enums.SportTracksDataTrackTypes.Cadence:
                        {
                            result = activityInfo.SmoothedCadenceTrack;
                            break;
                        }
                    case Enums.SportTracksDataTrackTypes.Elevation:
                        {
                            INumericTimeDataSeries baseTrack = activityInfo.SmoothedElevationTrack;
                            DateTime startTime = baseTrack.StartTime;

                            result = new NumericTimeDataSeries();
                            foreach (ITimeValueEntry<float> entry in baseTrack)
                            {
                                DateTime newEntryTime = startTime + new TimeSpan(0, 0, (int)entry.ElapsedSeconds);

                                result.Add(newEntryTime, (float)Length.Convert(entry.Value, Length.Units.Meter, activityCategory.ElevationUnits));
                            }
                            break;
                        }
                    case Enums.SportTracksDataTrackTypes.Grade:
                        {
                            result = activityInfo.SmoothedGradeTrack;
                            break;
                        }
                    case Enums.SportTracksDataTrackTypes.HeartRate:
                        {
                            result = activityInfo.SmoothedHeartRateTrack;
                            break;
                        }
                    case Enums.SportTracksDataTrackTypes.Speed:
                        {
                            INumericTimeDataSeries baseTrack = activityInfo.SmoothedSpeedTrack;
                            DateTime startTime = baseTrack.StartTime;

                            result = new NumericTimeDataSeries();
                            foreach (ITimeValueEntry<float> entry in baseTrack)
                            {
                                DateTime newEntryTime = startTime + new TimeSpan(0, 0, (int)entry.ElapsedSeconds);
                                float newEntryValue;

                                if (activityCategory.SpeedUnits == Speed.Units.Speed)
                                {
                                    newEntryValue = (float)MeterPerSecToSpeed(entry.Value, activityCategory.DistanceUnits);
                                }
                                else
                                {
                                    newEntryValue = (float)MeterPerSecToPace(entry.Value, activityCategory.DistanceUnits);
                                }
                                
                                result.Add(newEntryTime, newEntryValue);
                            }
                            break;
                        }
                    case Enums.SportTracksDataTrackTypes.Power:
                        {
                            result = activityInfo.SmoothedPowerTrack;
                            break;
                        }
                    default:
                        {
                            break;
                        }
                }
            }

            return result;
        }

        // Same as above but with pauses removed
        public static INumericTimeDataSeries GetPauselessTrackFromActivity(IActivity activity, Enums.SportTracksDataTrackTypes dataType)
        {
            INumericTimeDataSeries trackWithPauses = GetTrackFromActivity(activity, dataType);

            return RemovePausedTimesInTrack(trackWithPauses, activity);
        }

        // The HR track is returned in the percent type passed in parameter with the pauses/stops included.
        public static INumericTimeDataSeries GetHRTrackInReferential(IActivity activity, HeartRate.PercentTypes referential, IAthlete athlete)
        {
            INumericTimeDataSeries baseTrack = GetTrackFromActivity(activity, Enums.SportTracksDataTrackTypes.HeartRate);

            if (referential != HeartRate.PercentTypes.None && athlete != null)
            {
                IAthleteInfoEntry athleteEntry = athlete.InfoEntries.LastEntryAsOfDate(DateTime.Now);

                if(athleteEntry != null && !float.IsNaN(athleteEntry.MaximumHeartRatePerMinute))
                {
                    INumericTimeDataSeries result = new NumericTimeDataSeries();
                    DateTime startTime = baseTrack.StartTime;

                    foreach (ITimeValueEntry<float> entry in baseTrack)
                    {
                        DateTime newEntryTime = startTime + new TimeSpan(0, 0, (int)entry.ElapsedSeconds);

                        result.Add(newEntryTime, HeartRate.ToPercent(entry.Value, athlete, referential));
                    }

                    return result;
                }
            }

            return baseTrack;
        }

        // Same as above but with pauses removed
        public static INumericTimeDataSeries GetPauselessTrackFromActivity(IActivity activity, HeartRate.PercentTypes referential, IAthlete athlete)
        {
            INumericTimeDataSeries trackWithPauses = GetHRTrackInReferential(activity, referential, athlete);

            return RemovePausedTimesInTrack(trackWithPauses, activity);
        }

        public static bool ActivityContainsDataType(IActivity activity, Enums.SportTracksDataTrackTypes dataType)
        {
            List<IActivity> activityList = new List<IActivity>();

            activityList.Add(activity);
            return ActivitiesContainDataType(activityList, dataType);
        }

        public static bool ActivitiesContainDataType(IList<IActivity> activities, Enums.SportTracksDataTrackTypes dataType)
        {
            if (activities != null)
            {
                foreach (IActivity currentActivity in activities)
                {
                    INumericTimeDataSeries dataTrack = GetTrackFromActivity(currentActivity, dataType);

                    if (!IsNullOrEmptyTrack(dataTrack))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        public static void GetCommonDistanceUnitForActivities(IList<IActivity> activities, IApplication application,
                                                              out Length.Units distanceUnit, out Speed.Units speedUnit)
        {
            distanceUnit = Length.Units.Kilometer;
            speedUnit = Speed.Units.Speed;

            if (application != null)
            {
                distanceUnit = application.SystemPreferences.DistanceUnits;
                speedUnit = application.Logbook.ActivityCategories[0].SpeedUnits;

                if (activities != null && activities.Count > 0)
                {
                    distanceUnit = activities[0].Category.DistanceUnits;
                    speedUnit = activities[0].Category.SpeedUnits;

                    foreach (IActivity activity in activities)
                    {
                        // When selected activities distance units don't match, revert to system default
                        if (activity.Category.DistanceUnits != distanceUnit)
                        {
                            distanceUnit = application.SystemPreferences.DistanceUnits;
                        }

                        // When selected activities speed units don't match, revert to system default
                        if (activity.Category.SpeedUnits != speedUnit)
                        {
                            speedUnit = application.Logbook.ActivityCategories[0].SpeedUnits;
                        }
                    }
                }
            }
        }

        public static Length.Units GetCommonElevationUnitForActivities(IList<IActivity> activities, IApplication application)
        {
            Length.Units elevationUnit = Length.Units.Meter;

            if (application != null)
            {
                elevationUnit = application.SystemPreferences.ElevationUnits;

                if (activities != null && activities.Count > 0)
                {
                    elevationUnit = activities[0].Category.ElevationUnits;

                    foreach (IActivity activity in activities)
                    {
                        // When selected activities elevation units don't match, revert to system default
                        if (activity.Category.ElevationUnits != elevationUnit)
                        {
                            elevationUnit = application.SystemPreferences.ElevationUnits;
                            break;
                        }
                    }
                }
            }

            return elevationUnit;
        }

        // Removes all the points contained in the NonMovingTimes of the activity, excluding the Lower value (]Lower, Upper])
        public static INumericTimeDataSeries RemovePausedTimesInTrack(INumericTimeDataSeries sourceTrack, IActivity activity)
        {
            if (activity != null && sourceTrack != null)
            {
                ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity);

                return RemoveRangesInTrack(sourceTrack, activityInfo.NonMovingTimes);
            }

            return null;
        }

        // Removes all the points contained in the list of ValueRange excluding the Lower value (]Lower, Upper])
        public static INumericTimeDataSeries RemoveRangesInTrack(INumericTimeDataSeries sourceTrack,
                                                                 IValueRangeSeries<DateTime> rangesToRemove)
        {
            if (sourceTrack != null)
            {
                INumericTimeDataSeries result = new NumericTimeDataSeries();

                if (rangesToRemove == null || rangesToRemove.Count == 0)
                {
                    // Remove invalid data nonetheless
                    IEnumerator<ITimeValueEntry<float>> sourceEnumerator = sourceTrack.GetEnumerator();
                    bool sourceEnumeratorIsValid;

                    sourceEnumeratorIsValid = sourceEnumerator.MoveNext();

                    while (sourceEnumeratorIsValid)
                    {
                        if (!float.IsNaN(sourceEnumerator.Current.Value))
                        {
                            result.Add(sourceTrack.StartTime + new TimeSpan(0, 0, (int)sourceEnumerator.Current.ElapsedSeconds),
                                       sourceEnumerator.Current.Value);
                        }

                        sourceEnumeratorIsValid = sourceEnumerator.MoveNext();
                    }
                }
                else
                {
                    DateTime currentTime = sourceTrack.StartTime;
                    IEnumerator<ITimeValueEntry<float>> sourceEnumerator = sourceTrack.GetEnumerator();
                    IEnumerator<IValueRange<DateTime>> pauseEnumerator = rangesToRemove.GetEnumerator();
                    double totalPausedTimeToDate = 0;
                    bool sourceEnumeratorIsValid;
                    bool pauseEnumeratorIsValid;

                    pauseEnumeratorIsValid = pauseEnumerator.MoveNext();
                    sourceEnumeratorIsValid = sourceEnumerator.MoveNext();

                    while (sourceEnumeratorIsValid)
                    {
                        bool addCurrentSourceEntry = true;
                        bool advanceCurrentSourceEntry = true;

                        // Loop to handle all pauses up to this current track point
                        if (pauseEnumeratorIsValid)
                        {
                            if (currentTime > pauseEnumerator.Current.Lower &&
                                currentTime <= pauseEnumerator.Current.Upper)
                            {
                                addCurrentSourceEntry = false;
                            }
                            else if (currentTime > pauseEnumerator.Current.Upper)
                            {
                                // Advance pause enumerator
                                totalPausedTimeToDate += (pauseEnumerator.Current.Upper - pauseEnumerator.Current.Lower).TotalSeconds;
                                pauseEnumeratorIsValid = pauseEnumerator.MoveNext();

                                // Make sure we retry with the next pause
                                addCurrentSourceEntry = false;
                                advanceCurrentSourceEntry = false;
                            }
                        }

                        if (addCurrentSourceEntry && !float.IsNaN(sourceEnumerator.Current.Value))
                        {
                            DateTime entryTime = currentTime - new TimeSpan(0, 0, (int)totalPausedTimeToDate);

                            result.Add(entryTime, sourceEnumerator.Current.Value);
                        }

                        if (advanceCurrentSourceEntry)
                        {
                            sourceEnumeratorIsValid = sourceEnumerator.MoveNext();
                            currentTime = sourceTrack.StartTime + new TimeSpan(0, 0, (int)sourceEnumerator.Current.ElapsedSeconds);
                        }
                    }
                }

                return result;
            }

            return null;
        }

        // Removes all the points not contained in the ValueRange excluding the Lower value (]Lower, Upper]).
        //  Contrary to the RemoveRangesInTrack method, this one doesn't change the DateTime of the points
        //  in the track.  It is therefore safe to use with Calculate
        public static INumericTimeDataSeries IsolateRangeInTrack(INumericTimeDataSeries sourceTrack,
                                                                 IValueRange<DateTime> rangeToKeep)
        {
            if (sourceTrack != null && rangeToKeep != null)
            {
                INumericTimeDataSeries result = new NumericTimeDataSeries();
                double rangeStart = (rangeToKeep.Lower - sourceTrack.StartTime).TotalSeconds;
                double rangeEnd = (rangeToKeep.Upper - sourceTrack.StartTime).TotalSeconds;

                foreach (ITimeValueEntry<float> entry in sourceTrack)
                {
                    if (rangeStart <= entry.ElapsedSeconds &&
                        rangeEnd >= entry.ElapsedSeconds)
                    {
                        result.Add(sourceTrack.StartTime + new TimeSpan(0, 0, (int)entry.ElapsedSeconds),
                                    entry.Value);
                    }
                }

                return result;
            }

            return sourceTrack;
        }

        /*
         *      The following methods have been commented out because they don't yet have their respective
         *      unit tests.  If you use them, please add corresponding tests to validate behavior.  Activities
         *      for each test should be added in the AutoTest.logbook3 logbook at the current date.  You must
         *      validate that your are using the propoer activity in you test in order to avoid false positives.
         *      
 
        public static IGPSRoute RemovePausedTimesInTrack(IGPSRoute sourceTrack, IActivity activity)
        {
            ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity);

            if (activityInfo != null && sourceTrack != null)
            {
                if (activityInfo.NonMovingTimes.Count == 0)
                {
                    return sourceTrack;
                }
                else
                {
                    IGPSRoute result = new GPSRoute();
                    DateTime currentTime = sourceTrack.StartTime;
                    IEnumerator<ITimeValueEntry<IGPSPoint>> sourceEnumerator = sourceTrack.GetEnumerator();
                    IEnumerator<IValueRange<DateTime>> pauseEnumerator = activityInfo.NonMovingTimes.GetEnumerator();
                    double totalPausedTimeToDate = 0;
                    bool sourceEnumeratorIsValid;
                    bool pauseEnumeratorIsValid;

                    pauseEnumeratorIsValid = pauseEnumerator.MoveNext();
                    sourceEnumeratorIsValid = sourceEnumerator.MoveNext();

                    while (sourceEnumeratorIsValid)
                    {
                        bool addCurrentSourceEntry = true;
                        bool advanceCurrentSourceEntry = true;

                        // Loop to handle all pauses up to this current track point
                        if (pauseEnumeratorIsValid)
                        {
                            if (currentTime >= pauseEnumerator.Current.Lower &&
                                currentTime <= pauseEnumerator.Current.Upper)
                            {
                                currentTime = pauseEnumerator.Current.Lower;
                            }
                            else if (currentTime > pauseEnumerator.Current.Upper)
                            {
                                // Advance pause enumerator
                                totalPausedTimeToDate += (pauseEnumerator.Current.Upper - pauseEnumerator.Current.Lower).TotalSeconds;
                                pauseEnumeratorIsValid = pauseEnumerator.MoveNext();

                                // Make sure we retry with the next pause
                                addCurrentSourceEntry = false;
                                advanceCurrentSourceEntry = false;
                            }
                        }

                        if (addCurrentSourceEntry)
                        {
                            result.Add(currentTime.ToLocalTime() - new TimeSpan(0, 0, (int)totalPausedTimeToDate), sourceEnumerator.Current.Value);
                        }

                        if (advanceCurrentSourceEntry)
                        {
                            sourceEnumeratorIsValid = sourceEnumerator.MoveNext();
                            currentTime = sourceTrack.StartTime + new TimeSpan(0, 0, (int)sourceEnumerator.Current.ElapsedSeconds);
                        }
                    }

                    return result;
                }
            }

            return null;
        }

        public static DateTime RemovePausedTimes(DateTime activityTime, IActivity activity)
        {
            if (activity != null)
            {
                ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity);

                if (activityInfo != null &&
                    activityInfo.NonMovingTimes.Count > 0)
                {
                    TimeSpan totalPausedTimeToDate = new TimeSpan();

                    foreach (IValueRange<DateTime> pausedRange in activityInfo.NonMovingTimes)
                    {
                        // Pause before our time
                        if (pausedRange.Upper < activityTime)
                        {
                            totalPausedTimeToDate += (pausedRange.Upper - pausedRange.Lower);
                        }
                        else if (activityTime >= pausedRange.Lower &&
                                 activityTime <= pausedRange.Upper)
                        {
                            // Inside a stop, add time difference
                            totalPausedTimeToDate += (activityTime - pausedRange.Lower);
                        }
                    }

                    return activityTime - totalPausedTimeToDate;
                }
                else
                {
                    return activityTime;
                }
            }

            return new DateTime();
        }

        public static TimeSpan GetActiveActivityTime(IActivity activity)
        {
            ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity);
            TimeSpan activeTime = activityInfo.ActualTrackTime;

            // The only way to get the active time considering the settings is to use the non moving times
            foreach(IValueRange<DateTime> nonMovingTime in activityInfo.NonMovingTimes)
            {
                activeTime -= nonMovingTime.Upper - nonMovingTime.Lower;
            }

            return activeTime;
        }

        public static IValueRange<DateTime> AddPausesToRange(IValueRange<DateTime> source, IActivity activity)
        {
            if (activity != null)
            {
                ActivityInfo activityInfo = ActivityInfoCache.Instance.GetInfo(activity);

                if(activityInfo.NonMovingTimes.Count == 0)
                {
                    return source;
                }
                else
                {
                    DateTime lowerResultBound = new DateTime(source.Lower.Ticks);
                    DateTime upperResultBound = new DateTime(source.Upper.Ticks);

                    foreach (ValueRange<DateTime> pause in activityInfo.NonMovingTimes)
                    {
                        TimeSpan pauseDuration = pause.Upper - pause.Lower;

                        if (pause.Lower <= lowerResultBound)
                        {
                            // Lower bound after start of pause, add paused time to lower
                            lowerResultBound += pauseDuration;
                        }

                        if (pause.Lower <= upperResultBound)
                        {
                            // Lower bound after start of pause, add paused time to upper
                            upperResultBound += pauseDuration;
                        }
                    }

                    return new ValueRange<DateTime>(lowerResultBound, upperResultBound);
                }
            }

            return null;
        }

        public static IValueRangeSeries<DateTime> AddPausesToRanges(IValueRangeSeries<DateTime> source, IActivity activity)
        {
            if (activity != null)
            {
                ValueRangeSeries<DateTime> result = new ValueRangeSeries<DateTime>();

                foreach (IValueRange<DateTime> currentRange in source)
                {
                    result.Add(AddPausesToRange(currentRange, activity));
                }

                return result;
            }

            return null;
        }

        public static int CompareDateTimeRangeAscending(ValueRange<DateTime> item1, ValueRange<DateTime> item2)
        {
            return (int)(item1.Lower - item2.Lower).TotalSeconds;
        }
*/
    }
}
